use proto_bytes::{ice::Proto, message::ReqRef};
use utils::bincodec::Decode;

use crate::{cmd, cp_heat::driver::sensor, error::AppErr, task::handler_task::Response};

use super::{
    draw_water_task,
    driver::{ctrl, ice_uart, motor, nozzle_motor, APP_INFO},
    service::{self, draw_water},
    steam_heat_task, water_heat_task, HeatErr,
};

pub fn on_simple_req(req: ReqRef) -> Result<Response, AppErr> {
    let cmd = req.cmd();
    match cmd {
        cmd::heat::STIR_MOTOR => req
            .parse()
            .ok_or(AppErr::Parse)
            .and_then(|req| on_stir_motor_ctrl(req)),

        cmd::heat::PARTS_CTRL => req
            .parse()
            .ok_or(AppErr::Parse)
            .and_then(|req| on_parts_ctrl(req)),

        cmd::heat::ICE_CMD => req
            .parse_with_decode()
            .ok_or(AppErr::Parse)
            .and_then(|req| on_ice_cmd(req)),

        cmd::heat::DRAW_HEAT_CTRL => req
            .parse()
            .ok_or(AppErr::Parse)
            .and_then(|req| on_draw_heat(req)),

        cmd::heat::SET_ARG => req
            .parse()
            .ok_or(AppErr::Parse)
            .and_then(|req| on_set_arg(req)),

        cmd::heat::GET_ARG => req
            .parse()
            .ok_or(AppErr::Parse)
            .and_then(|req| on_get_arg(req)),

        cmd::heat::STEAM_CTRL => req
            .parse()
            .ok_or(AppErr::Parse)
            .and_then(|req| on_steam_ctrl(req)),

        _ => Err(AppErr::InvalidCmd),
    }
}

pub fn on_request(req: ReqRef) -> Result<Response, AppErr> {
    let cmd = req.cmd();
    match cmd {
        cmd::heat::STIR_CAP => req
            .parse()
            .ok_or(AppErr::Parse)
            .and_then(|req| on_stir_cap_ctrl(req)),

        cmd::heat::DRAW_WATER => req
            .parse()
            .ok_or(AppErr::Parse)
            .and_then(|req| on_draw_water(req)),

        cmd::heat::CLAMP_CTRL => req
            .parse()
            .ok_or(AppErr::Parse)
            .and_then(|req| on_clamp_ctrl(req)),

        cmd::heat::TRUN_CTRL => req
            .parse()
            .ok_or(AppErr::Parse)
            .and_then(|req| on_trun_ctrl(req)),

        _ => Err(AppErr::InvalidCmd),
    }
}

fn on_trun_ctrl(ctrl: u8) -> Result<Response, AppErr> {
    match ctrl {
        0 => service::trun_move()?,
        1 => service::trun_reset()?,
        _ => return Err(AppErr::Invalid),
    }
    Ok(Response::Empty(()))
}

fn on_clamp_ctrl(ctrl: u8) -> Result<Response, AppErr> {
    match ctrl {
        0 => service::clamp_open()?,
        1 => service::clamp_close()?,
        _ => return Err(AppErr::Invalid),
    }
    Ok(Response::Empty(()))
}

fn on_steam_ctrl(ctrl: u8) -> Result<Response, AppErr> {
    match ctrl {
        0 => draw_water::draw_steam_start(),
        1 => draw_water::draw_steam_stop(),
        _ => return Err(AppErr::Invalid),
    }
    Ok(Response::Empty(()))
}

fn on_draw_water((id, ml): (u8, u16)) -> Result<Response, AppErr> {
    match id {
        0 => draw_water::draw_water_with_v3(ml)?,
        1 => draw_water::draw_water_with_v4(ml)?,
        _ => return Err(AppErr::Invalid),
    }
    Ok(Response::Empty(()))
}

fn on_get_arg(id: u8) -> Result<Response, AppErr> {
    let value = match id {
        0 => APP_INFO.lock().water_temp as u16,
        1 => APP_INFO.lock().steam_temp as u16,
        2 => APP_INFO.lock().draw_water_timeout,
        3 => APP_INFO.lock().steam_heat_timeout,
        4 => APP_INFO.lock().water_heat_timeout,
        5 => APP_INFO.lock().flow_1000l_puls_count,
        _ => return Err(AppErr::Invalid),
    };
    Ok(Response::U16(value))
}

fn on_set_arg((id, value): (u8, u16)) -> Result<Response, AppErr> {
    match id {
        0 => {
            if value > 95 {
                return Err(AppErr::Invalid);
            }
            APP_INFO.lock().water_temp = value as i16;
        }
        1 => {
            if value > 120 {
                return Err(AppErr::Invalid);
            }
            APP_INFO.lock().steam_temp = value as i16;
        }
        2 => APP_INFO.lock().draw_water_timeout = value,
        3 => APP_INFO.lock().steam_heat_timeout = value,
        4 => APP_INFO.lock().water_heat_timeout = value,
        5 => {
            if value == 0 {
                return Err(AppErr::Heat(HeatErr::FlowArgInvalid));
            }
            APP_INFO.lock().flow_1000l_puls_count = value;
        }
        _ => return Err(AppErr::Invalid),
    };
    APP_INFO.lock().store().map_err(|_| HeatErr::Store)?;
    Ok(Response::Empty(()))
}

fn on_draw_heat(ctrl: u8) -> Result<Response, AppErr> {
    match ctrl {
        0 => draw_water_task::ctrl(draw_water_task::State::Stop),
        1 => draw_water_task::ctrl(draw_water_task::State::Exec),
        2 => water_heat_task::ctrl(water_heat_task::State::STOP),
        3 => water_heat_task::ctrl(water_heat_task::State::IDLE),
        4 => steam_heat_task::ctrl(steam_heat_task::State::Release),
        5 => steam_heat_task::ctrl(steam_heat_task::State::PreHeat),
        _ => return Err(AppErr::Invalid),
    }
    Ok(Response::Empty(()))
}

#[derive(Clone, Copy, Decode)]
pub struct IceData {
    pub cmd2: u8,
    pub data1: u8,
    pub data2: u8,
}

fn on_ice_cmd(data: IceData) -> Result<Response, AppErr> {
    let ret = ice_uart::cmd(Proto {
        cmd2: data.cmd2,
        data1: data.data1,
        data2: data.data2,
    })?;
    Ok(Response::Ice(IceData {
        cmd2: ret.cmd2,
        data1: ret.data1,
        data2: ret.data2,
    }))
}

pub fn on_pearl_motor(position: u32) -> Result<Response, AppErr> {
    nozzle_motor::run(position)?;
    Ok(Response::Empty(()))
}

fn on_parts_ctrl((id, ctrl): (u8, u8)) -> Result<Response, AppErr> {
    let v = ((id as u16) << 8) | (ctrl as u16);
    match v {
        0x0000 => ctrl::heat_steam_open(),
        0x0001 => ctrl::heat_steam_close(),

        0x0100 => ctrl::heat_water_open(),
        0x0101 => ctrl::heat_water_close(),

        0x0200 => ctrl::v3_open(),
        0x0201 => ctrl::v3_close(),

        0x0300 => ctrl::v4_open(),
        0x0301 => ctrl::v4_close(),

        0x0400 => ctrl::bucket2_pump_open(),
        0x0401 => ctrl::bucket2_pump_close(),

        0x0500 => ctrl::bucket1_pump_open(),
        0x0501 => ctrl::bucket1_pump_close(),

        0x0600 => ctrl::v6_open(),
        0x0601 => ctrl::v6_close(),

        0x0700 => ctrl::v7_open(),
        0x0701 => ctrl::v7_close(),

        0x0800 => ctrl::v2_open(),
        0x0801 => ctrl::v2_close(),

        0x0900 => ctrl::v1_open(),
        0x0901 => ctrl::v1_close(),

        0x0A00 => ctrl::clamp_valve_open(),
        0x0A01 => ctrl::clamp_valve_close(),

        0x0B00 => ctrl::trun_valve_open(),
        0x0B01 => ctrl::trun_valve_close(),

        0x0C00 => ctrl::water_pump_open(),
        0x0C01 => ctrl::water_pump_close(),

        0x0D00 => ctrl::steam_pump_open(),
        0x0D01 => ctrl::steam_pump_close(),

        0x0E00 => ctrl::v8_open(),
        0x0E01 => ctrl::v8_close(),

        0x0F00 => ctrl::air_pump_open(),
        0x0F01 => ctrl::air_pump_close(),

        _ => return Err(AppErr::Invalid),
    }
    Ok(Response::Empty(()))
}

fn on_stir_motor_ctrl(ctrl: u8) -> Result<Response, AppErr> {
    match ctrl {
        0 => {
            if !sensor::is_stir_bottom() {
                return Err(AppErr::Heat(HeatErr::StirCapNotClose));
            }
            motor::stir_open();
        }
        1 => motor::stir_close(),
        _ => return Err(AppErr::Invalid),
    }
    Ok(Response::Empty(()))
}

fn on_stir_cap_ctrl(ctrl: u8) -> Result<Response, AppErr> {
    match ctrl {
        0 => {
            motor::stir_close();
            motor::stir_cap_open()?;
        }
        1 => motor::stir_cap_close()?,
        _ => return Err(AppErr::Invalid),
    };
    Ok(Response::Empty(()))
}
